﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using ALDIOrganizer.Properties;

namespace ALDIOrganizer
{
    public partial class SheetView : DataGridViewPopup
    {
        internal PopupMenuLayout PopupLayout;

        #region binding data
        private bool eventsActivated;

        public void ActivateEvents()
        {
            eventsActivated = true;
        }

        public void DeactivateEvents()
        {
            eventsActivated = false;
        }

        internal Rota refillRota;
        internal Template refill_template;
        internal Sheet refill
        {
            get
            {
                switch (Mode)
                {
                    case SheetType.Rota:
                        {
                            return refillRota;
                        }
                    default: // SheetType.Template (there's only two options!)
                        {
                            return refill_template;
                        }
                }
            }
            set
            {
                refill = value;
            }
        }
        private SheetType Mode;        

        private bool _bound;
        public bool Bound
        {
            get
            {
                return _bound;
            }
        }

        private bool _areWeEditing;
        private bool _shallWeUpdate;
        private ShiftTandem _shiftsToAdd;
                
        public delegate void RefreshTotalHoursEventHandler(SumOfHours? pTotalHours, SumOfHours? pTotalContractHours);
        public delegate bool delBeginEditOnShutDay(Weekday day);
        public delegate void OnCellEnteredEventHandler(Cell pCell);
        public delegate void OnMustRecalculateDiscrepanciesEventHandler(RowTag[] pTags, Weekday[] pDays);
        public event OnMustRecalculateDiscrepanciesEventHandler OnMustRecalculateDiscrepancies;
        
        public event RefreshTotalHoursEventHandler OnRefreshTotalHours;        
        public event OnCellEnteredEventHandler OnCellEntered;

        private void Bind(SheetType pMode)
        {
            Mode = pMode;
            _bound = true;
            ReadHeaderFormat();
        }

        public void Bind(Sheet pRefill, bool pSetComboBoxColumnDataSource)
        {
            pRefill.OnCellValueChanged += pRefill_OnCellValueChanged;
            pRefill.OnCellStyleChanged += pRefill_OnCellStyleChanged;
            
            PopupLayout = new PopupMenuLayout();
            
            (pRefill as Sheet).OnDataChanged += Refill_OnDataChanged;
            if (pRefill is Rota)
            {
                refillRota = pRefill as Rota;
                refillRota.OnDataChanged += Refill_OnDataChanged;
                refillRota.OnDayStatusesUpdated += refill_rota_OnDayStatusesUpdated;
                Bind(SheetType.Rota);                        
            }
            if (pRefill is Template)
            {
                refill_template = pRefill as Template;
                refill_template.OnDataChanged += Refill_OnDataChanged;                
                Bind(SheetType.Template);

                if (pSetComboBoxColumnDataSource)
                {
                    var comboboxColumn = Columns[0] as DataGridViewComboBoxColumn;
                    comboboxColumn.DataSource = DataGridViewOpeningHrs.GetOptionsForAddAnother;
                    comboboxColumn.DisplayMember = "PositionName";
                    comboboxColumn.ValueMember = "position";
                }
            }                        
        }

        void pRefill_OnCellStyleChanged(Cell pCell)
        {
            var row = GetRowByTag(pCell.Tag);
            if (row != null)
            {
                this[pCell.GetColumn, row.Index].ApplyStyle(pCell.CellStyle);
            }
        }

        void refill_rota_OnDayStatusesUpdated(Weekday[] pDays)
        {
            foreach (var _day in pDays)
            {                
                refillRota.RefreshColumn(_day);
                RefreshColumnHeader(_day.ToInt1());
            }
        }        

        void pRefill_OnCellValueChanged(Cell pCell)
        {
            var _row = GetRowByTag(pCell.Tag);
            if (_row == null)
            {
                return;
            }
            this[pCell.GetColumn, _row.Index].Value = pCell.Value;

            if (CurrentCell != null)
            {
                if (this[pCell.GetColumn, _row.Index] == CurrentCell)
                {
                    Refresh();
                }
            }
        }


        public void RefreshRefillRota()
        {
            if (!Bound |
                Mode == SheetType.Template)                
            {
                return;
            }
            refillRota.Refresh();
            Display();
        }

        public void Refill_OnDataChanged(RowTag[] rowtags, Weekday[] days)
        {
            SuspendLayout();
            if (refill.Mode == SheetType.Rota)
                foreach (RowTag tag in rowtags)
                {
                    refillRota.Employees.RefreshEmployee(tag.Int64Value);
                    RefreshEmployeesHours(tag.Int64Value);
                }

            if (Mode == SheetType.Rota)
            {
                refillRota.UpdateBottomBar(days);
            }
            foreach (var day in days)
            {
                var col = day.ToInt1();
                RefreshColumnHeader(col);                                
            }
            var _totalHours = refill.TotalHours();

            OnRefreshTotalHours((SumOfHours?)_totalHours[7],
                (refill.Mode == SheetType.Rota) ?
                (SumOfHours?)refillRota.GetSumOfContractHours :
                (SumOfHours?)null);
            if (OnMustRecalculateDiscrepancies != null)
            {
                OnMustRecalculateDiscrepancies(rowtags, days);
            }

            Refresh();
            ResumeLayout();
        }

        public void TotalRefresh()
        {            
            Refill_OnDataChanged(refill.GetAllTags,
                Time.AllWeekdays);            
        }

        public void TotalRefresh(Weekday[] pWeekdays)
        {
            Refill_OnDataChanged(refill.GetAllTags,
                pWeekdays);
        }

        protected override void OnDataError(bool displayErrorDialogIfNoHandler, DataGridViewDataErrorEventArgs e)
        {
            
        }
                
        protected override void OnCellBeginEdit(DataGridViewCellCancelEventArgs e)
        {
            if (!eventsActivated | !Bound)
            {
                return;
            }

            _shallWeUpdate = false;

            if (!(this[e.ColumnIndex, e.RowIndex] is DataGridViewComboBoxCell))
            {
                if (!Underlying(e.ColumnIndex, e.RowIndex).IsEditable)
                {
                    e.Cancel = true;
                    return;
                }
            }
            
            this[e.ColumnIndex, e.RowIndex].Value = String.Empty;
            _areWeEditing = true;                        
        }

                

        protected override void OnCellValidating(DataGridViewCellValidatingEventArgs e)
        {
            if (!eventsActivated) return;
            #region Initial checks


            int row = e.RowIndex;
            int col = e.ColumnIndex;

            /* CellValidating is triggered every Time user moves the cursor from one Cell to another,
             * so we are id1 making sure if any validation is "really" needed. */

            // _areWeEditing gets set to true in CellBeginEdit event. If it'pShift false, means user is just navigating.
            if ((!_areWeEditing)

                // we do not validate uneditable Cells (candidate.g. row Name in the id1 column)
                || !Underlying(e.ColumnIndex, e.RowIndex).IsEditable

                // whatever launched CellValidating, it couldn't have been user'pShift action, or else Value wouldn't be null
                || (e.FormattedValue == null)

                // this one is not actually needed (since user can't AddAnotherRowString new rows directly), but "just in case".
                || Rows[row].IsNewRow)

                return;
            #endregion

            // user could have manually delete the contents of the Cell
            var cleanedCellUp = e.FormattedValue.ToString().Trim() == "";

            #region parsing and rejecting invalid input
            if (!cleanedCellUp)
            {
                var input = e.FormattedValue.ToString();
                try
                {
                    _shiftsToAdd = Shift.ToShifts(input, refill); // what does it need Rota for?
                }
                catch (ArgumentException)
                {
                    e.Cancel = true;
                    MessageBox.Show(
                        Resources.MESSAGEBOX_ERROR_Validation_NoTimeTravellers_MESSAGE,
                        Resources.MESSAGEBOX_ERROR_Validation_NoTimeTravellers_CAPTION,
                        MessageBoxButtons.OK,
                        MessageBoxIcon.Error);
                    return;
                }
                if (_shiftsToAdd == null ||
                    _shiftsToAdd.Count == 0)
                {
                    e.Cancel = true;
                    MessageBox.Show(
                        Resources.MESSAGEBOX_ERROR_Validation_UnspecifiedError_MESSAGE,
                        Resources.MESSAGEBOX_ERROR_Validation_UnspecifiedError_CAPTION,
                        MessageBoxButtons.OK,
                        MessageBoxIcon.Error);
                    return;
                }
                if (_shiftsToAdd.Count > 1)
                {
                    if (Mode == SheetType.Template)
                    {
                        e.Cancel = true;
                        MessageBox.Show(
                            Resources.MESSAGEBOX_ERROR_Validation_NoMultipleShiftsTemplate_MESSAGE,
                            Resources.MESSAGEBOX_ERROR_Validation_NoMultipleShiftsTemplate_CAPTION,
                            MessageBoxButtons.OK,
                            MessageBoxIcon.Error);
                        return;
                    }
                    if (Shift.DoOverlap(_shiftsToAdd.ToArray()))
                    {
                        e.Cancel = true;
                        MessageBox.Show(
                            Resources.MESSAGEBOX_ERROR_Validation_ShiftsCannotOverlap_MESSAGE,
                            Resources.MESSAGEBOX_ERROR_Validation_ShiftsCannotOverlap_CAPTION,
                            MessageBoxButtons.OK,
                            MessageBoxIcon.Error);
                        return;
                    }
                }
            }
            #endregion

            // we clear the "old" Shifts anyway (whether there are new ones coming or not)
            var underlying = Underlying(col, row);
            if (refill.Mode == SheetType.Rota)
            {
                refillRota.RemoveShifts((Weekday)(col-1),
                    underlying.Tag,
                    false);
            }
            else
            {
                refill_template.RemoveShifts((Weekday)(col-1),
                    underlying.Tag,
                    false);
            }

            if (cleanedCellUp)
            {
                underlying.Update();
                this[e.ColumnIndex, e.RowIndex].Value = underlying.Value;

                Sounds.Play(Sounds.Validated);
                return;
            }
            /* Originally Rota and Template class used to share one common Sheet.AddShifts method
             * (their GetParent class'pShift method)
             * but there were problems with triggering the event handler so I split them */
            if (Mode == SheetType.Rota)
            {
                (refill as Rota).AddShifts(_shiftsToAdd, (Weekday)e.ColumnIndex - 1, underlying.Tag, true);
            }
            else
            {
                (refill as Template).AddShifts(_shiftsToAdd, (Weekday)e.ColumnIndex - 1, underlying.Tag, true);
            }
            _shallWeUpdate = true;
        }

        protected override void OnCellEndEdit(DataGridViewCellEventArgs e)
        {
            if (!eventsActivated)
            {
                return;
            }
            _areWeEditing = false;

            if (_shallWeUpdate)
            {
                // this[e.ColumnIndex, e.RowIndex].Value = underlying.Value; // TODO: hmmm                
                _shallWeUpdate = false;
            }

            // CurrentCell = this[e.ColumnIndex, e.RowIndex - 1];
            /* For now it should be disabled.
             * Not sure what situation this was needed for. */

            // Refill_OnDataChanged((e.RowIndex);
            // refreshTotalHours(e.ColumnIndex);

            Refresh();
        }
        
        protected override void OnKeyDown(KeyEventArgs e)
        {
            base.OnKeyDown(e);
            if (!eventsActivated | ReadOnly)
            {
                return;
            }
            int col = this.CurrentCell.ColumnIndex;
            int row = this.CurrentCell.RowIndex;
            var underlying = Underlying(col, row);

            if ((e.KeyCode == Keys.Delete)
                && (underlying.MyCellType == CellType.Shift))
            {
                var day = (Weekday)(col - 1);
                var tag = underlying.Tag;
                if (refill.Mode == SheetType.Rota)
                {
                    refillRota.RemoveShifts(
                        day,
                        tag,
                        true);
                }
                else
                {
                    refill_template.RemoveShifts(
                        day,
                        tag,
                        true);
                }
            }
            
        }

        protected override void OnCellFormatting(DataGridViewCellFormattingEventArgs e)
        {
            if (!eventsActivated)
            {
                return;
            }
            // overshadowing
            
            /* Colors are applied to certain Cells here.
             * A word of explanation is needed. We retrieve the DataGridView from sender
             * and we access the Cell in question directly. 
             * MSDN advices against it -
             * "to avoid performance penalties When handling this event,
             * access the Cell through the parameters of the event handler
             * rather than accessing the Cell directly."
             * 
             * The problem is that we don't want to set the same colour to the same Cell
             * over and over again.
             * And candidate.CellStyle always "forgets" what colour had the Cell had! */

            var col = e.ColumnIndex;
            var row = e.RowIndex;
            var underlying = Underlying(col, row);
            var cachedStyle = underlying.CellStyle;

            this[col, row].ApplyStyle(cachedStyle);

            if (underlying.MyCellType != CellType.JobPosition) // Cells of the "_role" MyCellType are special - combobox, databound. we leave them alone
            {
                if ((String)this[col, row].Value != underlying.Value)
                {
                    this[col, row].Value = underlying.Value;
                }
            }

        }

        protected override void  OnCellDoubleClick(DataGridViewCellEventArgs e)
        {
            base.OnCellDoubleClick(e);    
            var underlying = Underlying(e.ColumnIndex, e.RowIndex);
            if (e.RowIndex == -1)
            {
                AutoResizeColumns(DataGridViewAutoSizeColumnsMode.DisplayedCells);
                return;
            }
            if ((e.ColumnIndex == 0)
                && (Mode == SheetType.Template))
            {
                return;
            }
            if (!_areWeEditing &&
                underlying.IsEditable)
            {
                AddNotes(e.ColumnIndex, e.RowIndex);
            }
        }

        internal void AddNotes(int pColumn, int pRow)
        {
            if (pColumn < 1 | pRow < 0)
            {
                return;
            }
            var statusStripText = String.Empty;            
            var _cell = UnderlyingRow(pRow)[pColumn];
            if (!_cell.IsEditable)
            {
                return;
            }
            if (refill.Mode == SheetType.Rota)
            {
                statusStripText += GetEmployee(pRow).Employee.firstName;
            }
            else
            {
                statusStripText += new StaffRole((Position)this[0, pRow].Value).PositionName;
            }

            statusStripText += ", " + Time.NameOfDay((Weekday)pColumn - 1);

            if (_cell.MyCellType == CellType.Shift)
            {
                statusStripText += " (";
                statusStripText += Shift.ToString(_cell.Shifts, false).
                    Replace(Environment.NewLine, String.Empty);
                statusStripText += ")";
            }

            Program.frmEditNotes = new EditNotes(statusStripText, _cell);

            if (ShowDialogAt(pColumn, pRow, Program.frmEditNotes, 25) == DialogResult.OK)
            {
                _cell.Notes = Program.frmEditNotes.notes;
                _cell.Update();
                Sounds.Play(Sounds.Validated);
            }
            Program.frmEditNotes.Dispose();
        }

        // for combobox
        protected override void OnCellValueChanged(DataGridViewCellEventArgs e)
        {
            if (!eventsActivated)
            {
                return;
            }
            #region Initial conditions
            if (Mode != SheetType.Template)
            {
                return;
            }
            if (e.ColumnIndex != 0)
            {
                return;
            }
            if (e.RowIndex < 0)
            {
                return;
            }
            #endregion

            var row = UnderlyingRow(e.RowIndex);
            var _newValue = this[0, e.RowIndex].Value;

            if (!(_newValue is Position))
            {
                return;
            }            
            ((Template)refill).SetRoleValue(
                row, 
                (Position)this[0, e.RowIndex].Value);
            SortTemplate();
            Sounds.Play(Sounds.Validated);
            if (OnMustRecalculateDiscrepancies != null)
            {
                OnMustRecalculateDiscrepancies(
                    new RowTag[] { row[0].Tag },
                    Time.AllWeekdays);
            }            
        }


        protected override void OnCellEnter(DataGridViewCellEventArgs e)
        {
            base.OnCellEnter(e);
            if (!eventsActivated)
            {
                return;
            }            
            var _cell = Underlying(e.ColumnIndex, e.RowIndex);
            if (OnCellEntered != null)
            {
                OnCellEntered(_cell);
            }
        }


        #endregion

        #region retrieving rows
        private DataGridViewRow GetRowByTag(RowTag tag)
        {
            if (tag.Int64Value == -1)
                if (Mode == SheetType.Rota)
                    return Rows[Rows.Count - 1];
                
            foreach(DataGridViewRow row in Rows)
                if (row.Tag != null)
                    if (row.Tag is RowTag)
                    {
                        var _tag = row.Tag as RowTag;
                        if (_tag.Equals(tag))
                            return row;
                    }
            
            
            return null;                
        }        

        public DataGridViewRow GetPhysicalRow(int i)
        {
            return this.Rows[i];
        }

        public DataGridViewRow GetRowByEmployeeID(Int64 empID)
        {
            return GetRowByTag(new RowTag(empID));
        }

        public EmployeeInRota GetEmployee(int pRow)
        {
            if (Mode != SheetType.Rota)
            {
                return null;
            }
            if ((pRow < 0)
                | (pRow >= Rows.Count - 1))
            {
                return null;
            }
            var _tag = Rows[pRow].Tag as RowTag;
            if (_tag == null)
            {
                return null;
            }
            return refillRota.Employees.GetEmployee(_tag.Int64Value);
        }

        internal IEnumerable<EmployeeInRota> GetEmployees()
        {
            return refillRota.Employees.getWorkers;
        }
        
        #endregion
        
        #region adding rows (marked with identifying tags)
        public DataGridViewRow SpawnNewRow(RowTag tag)
        {
            var row = new DataGridViewRow();
            var col = 0;
            if(tag.MyType == RowTagType.GUID)
            {
                var dcb = new DataGridViewComboBoxCell();
                var combobox_column = this.Columns[0] as DataGridViewComboBoxColumn;
                dcb.DataSource = combobox_column.DataSource;
                dcb.DisplayMember = combobox_column.DisplayMember;
                dcb.ValueMember = combobox_column.ValueMember;
                dcb.Tag = tag;
                row.Cells.Add(dcb);
                col++; // !
            }
            for (int i = col; i < Sheet.cols; i++)
            {
                row.Cells.Add(new DataGridViewTextBoxCell());
                row.Cells[i].Tag = tag;
            }
            row.Tag = tag;
            return row;
        }

        public void AddRow(StaffRole pos)
        {
            if (Mode != SheetType.Template)
            {
                return;
            }
            var row = SpawnNewRow(RowTag.GetNewGuidTag);
            row.Cells[0].Value = pos.position; // TODO: it looks like shit, honestly

            if (Bound)
            {
                refill_template.AddRow(
                    pos.position,
                    ((RowTag)row.Cells[0].Tag).GuidValue);
            }
            Rows.Add(row);
            SortTemplate();
            Sounds.Play(Sounds.Validated);                
        }

        public DataGridViewRow AddRowAndReturnIt(RowTag tag)
        {
            if (!tag.Match(Mode))
            {
                return null;
            }
            var newRow = SpawnNewRow(tag);            
            Rows.Add(newRow);                        
            return Rows[Rows.GetLastRow(DataGridViewElementStates.None)];
        }

        public void UpdateCells(DataGridViewRow row)
        {
            var tag = row.Cells[0].Tag as RowTag;
            if (tag == null)
            {
                return;
            }

            var underlying_row = refill[tag];
            for (int col = 0; col < row.Cells.Count; col++)
            {
                if (row.Cells[col].GetType() != typeof(DataGridViewComboBoxCell))
                {
                    // row.Cells[col].Value = underlying_row[col].Value;
                }
                else
                {
                    row.Cells[col].Value = underlying_row[col].RoleValue;
                }
            }
        }


        #endregion

        #region removing & sorting rows
        public void RemoveRow(RowTag tag)
        {
            foreach (var row in
                Rows.Cast<DataGridViewRow>().Where(row => row.Tag.Equals(tag)))
            {
                Rows.Remove(row);
            }
            if (!Bound)
            {
                return;
            }
            switch (tag.MyType)
            {
                case RowTagType.LongInt:
                    {
                        ((Rota) refill).RemoveRow(tag.Int64Value);
                        break;
                    }
                case RowTagType.GUID:
                    {
                        ((Template) refill).RemoveRow(tag.GuidValue);
                        OnMustRecalculateDiscrepancies(
                            new RowTag[] {tag},
                            Time.AllWeekdays);
                        OnRefreshTotalHours(
                            (SumOfHours?) refill.TotalHours()[7],
                            (SumOfHours?) null);
                        break;
                    }
            }
        }

        

        #endregion

        internal Cell Underlying(int col, int row)
        {
            return UnderlyingRow(row)[col];
        }

        internal Cell[] UnderlyingRow(int pRowIndex)
        {
            var tag = Rows[pRowIndex].Cells[0].Tag as RowTag;
            if (Mode != SheetType.Rota)
            {
                return refill[tag.GuidValue];
            }

            // Mode is SheetType.Template
            if (pRowIndex == Rows.Count - 1) // last row
            {
                return refillRota.BottomBar;
            }            
            return refill[tag.Int64Value];            
        }

        public int NaturalHeight()
        {         
            var height = ColumnHeadersHeight;
            for (int i = 0; i < RowCount; i++)
            {
                height += Rows[i].Height;
            }
            return height;                    
        }

        public void SetNaturalHeight(int pMaximumHeight)
        {                            
            // whichever is smaller
            Height = (NaturalHeight() < pMaximumHeight) ?
                NaturalHeight() :
                pMaximumHeight;        
        }
    }
}
